package com.learn;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

@Slf4j
public class MappedFileUtil {

    static String USER_DIR = System.getProperty("user.dir");

    /**
     * 读取文件为T的列表
     * @param clazz
     * @param fileName
     * @param <T>
     * @return
     */
    public static <T> List<T> readFromCachedFile(Class<T> clazz, String fileName) {
        log.info("readFromCachedFile {}, item type:{}",fileName,clazz);
        MappedByteBuffer mappedByteBuffer = null;
        if (MappedFileUtil.isFileExists(fileName)) {
            mappedByteBuffer = MappedFileUtil.getMappedByteBuffer(fileName);
        }
        if (mappedByteBuffer == null) {
            return null;
        }

        ArrayList<T> result = new ArrayList<>();
        byte[] tempBytes = new byte[1024];

        mappedByteBuffer.position(0);
        while (mappedByteBuffer.hasRemaining()) {
            /**
             * 读取每一项的长度
             */
            int entryLength = mappedByteBuffer.getInt();
            if (entryLength == 0) {
                break;
            }

            /**
             * 读取length那么长的字节数组
             */
            mappedByteBuffer.get(tempBytes, 0, entryLength);
            String entryValue = new String(tempBytes, 0, entryLength, StandardCharsets.UTF_8);

            log.info("read {}({}bytes) from buffer",entryValue,entryLength);
            T t = JSON.parseObject(entryValue, clazz);
            result.add(t);
        }


        return result;
    }


    /**
     * 判断文件是否存在
     * @param fileName
     * @return
     */
    private static boolean isFileExists(String fileName){
        File file = new File(USER_DIR, fileName);
        return file.exists();
    }

    /**
     * 获取已经存在的文件的mbb
     * @param fileName
     * @return
     */
    private static MappedByteBuffer getMappedByteBuffer(String fileName){
        File file = new File(USER_DIR, fileName);
        long length = file.length();

        /**
         * 进行内存映射
         */
        MappedByteBuffer mappedByteBuffer;
        FileChannel fileChannel = null;
        try {
            fileChannel = new RandomAccessFile(file, "rw").getChannel();
            mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, length);
        } catch (FileNotFoundException e) {
            log.error("Failed to find file " + fileName, e);
            throw new RuntimeException(e);
        } catch (IOException e) {
            log.error("Failed to map file " + fileName, e);
            throw new RuntimeException(e);
        } finally {
            if (fileChannel != null) {
                try {
                    fileChannel.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }

        return mappedByteBuffer;
    }


    /**
     * 映射文件
     * @param file
     * @param mappedRegionSizeInBytes
     * @return
     */
    private static MappedByteBuffer mapFileWithSpecifiedSize(File file,long mappedRegionSizeInBytes) {
        MappedByteBuffer mappedByteBuffer;
        FileChannel fileChannel = null;
        try {
            fileChannel = new RandomAccessFile(file, "rw").getChannel();
            mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, mappedRegionSizeInBytes);
        } catch (FileNotFoundException e) {
            log.error("Failed to find file " + file.getName(), e);
            throw new RuntimeException(e);
        } catch (IOException e) {
            log.error("Failed to map file " + file.getName(), e);
            throw new RuntimeException(e);
        } finally {
            if (fileChannel != null) {
                try {
                    fileChannel.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }

        return mappedByteBuffer;
    }


    /**
     * 写入文件
     * @param datas
     * @param fileName
     * @param <T>
     */
    public static  <T> void write2file(List<T> datas,String fileName) {
        /**
         * 判断文件是否存在，不存在则创建
         */
        File file = new File(USER_DIR, fileName);
        if (file.exists()) {

        } else {
            try {
                file.createNewFile();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }


        /**
         * 获取总的要写入的长度
         */
        int mappedRegionSizeInBytes = datas.stream().mapToInt(item -> {
            String jsonString = JSONObject.toJSONString(item);
            byte[] jsonStringBytes = jsonString.getBytes(StandardCharsets.UTF_8);
            /**
             * 4字节，是每个item的长度所占的字节
             */
            return jsonStringBytes.length + 4;
        }).sum();


        /**
         * 内存映射
         */
        MappedByteBuffer mappedByteBuffer = mapFileWithSpecifiedSize(file,mappedRegionSizeInBytes);

        /**
         * 开始写入;
         * 显式指定从0开始写
         */
        int offset = 0;
        mappedByteBuffer.position(offset);
        for (T t : datas) {

            /**
             * 序列化为json
             */
            String s = JSONObject.toJSONString(t);
            if (StringUtils.isEmpty(s) || "".equals(s.trim())) {
                continue;
            }
            /**
             * 先写入长度
             */
            byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
            mappedByteBuffer.putInt(bytes.length);

            /**
             * 写入内容
             */
            mappedByteBuffer.put(bytes);
        }

        /**
         * 刷入磁盘
         */
        mappedByteBuffer.force();
    }
}
